﻿using Gasgoo.ShareLibrary.Framework.Global;
using Gasgoo.ShareLibrary.Framework.iQueueProvider;
using Gasgoo.ShareLibrary.Framework.Options;
using Gasgoo.ShareLibrary.Framework.ServiceDefine;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Orleans;
using Orleans.Configuration;
using Orleans.Hosting;
using Orleans.Runtime;
using Orleans.Streams;
using System;
using System.Threading.Tasks;

namespace Gasgoo.ShareLibrary.Framework
{
    public interface GasgooClusterHostClient : IClusterService
    {
    }

    public class ClusterHostClient : GasgooClusterHostClient
    {
        readonly IClusterClient iClient;
        readonly IServiceProvider ServiceProvider;
        public ClusterHostClient(IClusterClient client)
        {
            this.iClient = client;
            this.ServiceProvider = client.ServiceProvider;

            var serviceCollection = new ServiceCollection();
            serviceCollection.AddSingleton(this.ServiceProvider.GetRequiredService<IClusterClient>());
            serviceCollection.AddSingleton<IClusterService>(this);
            serviceCollection.AddSingleton<GasgooClusterHostClient>(this);
            ServiceManage.RegisterServiceProvider(serviceCollection.BuildServiceProvider(), Constants.HOST_SERVICE_PROVIDER);
        }

        public T GetService<T>(Guid primaryKey) where T : GasgooServiceWithGuidKey
        {
            return this.iClient.GetGrain<T>(primaryKey);
        }

        public T GetService<T>(long primaryKey) where T : GasgooServiceWithIntegerKey
        {
            return this.iClient.GetGrain<T>(primaryKey);
        }

        public T GetService<T>(string primaryKey) where T : GasgooServiceWithStringKey
        {
            return this.iClient.GetGrain<T>(primaryKey);
        }

        public T GetService<T>(long primaryKey1, string primaryKey2) where T : GasgooServiceWithIntegerCompoundKey
        {
            return this.iClient.GetGrain<T>(primaryKey1, primaryKey2);
        }

        public T GetService<T>(Guid primaryKey1, string primaryKey2) where T : GasgooServiceWithGuidCompoundKey
        {
            return this.iClient.GetGrain<T>(primaryKey1, primaryKey2);
        }

        /// <summary>
        /// 获队列
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public IQueueProvider GetQueueProvider(string name)
        {
            IStreamProvider stream = this.iClient.GetStreamProvider(name);
            return new DefaultQueueProvider(stream);
        }

        public IManagement GetManagementer() => new ManagementService(this.iClient.GetGrain<IManagementGrain>(0));

        public T GetService<T>() where T : GasgooService
        {
            return this.iClient.GetGrain<T>(0);
        }

        public async Task StopAsync()
        {
            await this.iClient.DisposeAsync();
        }

        public T GetInterface<T>()
        {
#pragma warning disable CS8603 // 可能返回 null 引用。
            return this.ServiceProvider.GetService<T>();
#pragma warning restore CS8603 // 可能返回 null 引用。
        }
    }

    public class GasgooClientBuilder
    {

        readonly ClientBuilder builder;
        const int InitializeAttemptsBeforeFailing = 180;
        private int attempt = 0;
        private string? clusterId { get; set; }
        private Action? anavailable;
        private const string Invariant = "System.Data.SqlClient";

        public GasgooClientBuilder()
        {
            this.builder = new ClientBuilder();
            this.Ini();
            this.AddFromAppDomain();
        }

        void Ini()
        {
            this.builder.AddSimpleMessageStreamProvider(Constants.SMS_PROVIDER, (SimpleMessageStreamProviderOptions opt) =>
            {
                opt.FireAndForgetDelivery = true;
                opt.OptimizeForImmutableData = true;
                opt.PubSubType = StreamPubSubType.ExplicitGrainBasedAndImplicit;
            });
        }

        public GasgooClientBuilder UseAdoNetClustering(AdoNetClusterOptions clusterOptions)
        {
            this.clusterId = clusterOptions.ClusterOptions.ClusterId;
            this.builder.Configure<ClusterOptions>(options =>
            {
                options.ClusterId = clusterOptions.ClusterOptions.ClusterId;
                options.ServiceId = clusterOptions.ClusterOptions.ServiceId;
            });

            this.builder.ConfigureServices(services =>
            {
                services.AddSingleton(clusterOptions.AdoNetOptions);
                //services.AddSingleton<IKeyValueStorageProvider, SQLServerKeyValueStorageProvider>();
                //services.AddSingleton<IQueueStorageProvider, SQLServerQueueStorageProvider>();
            });

            this.builder.Configure<SiloMessagingOptions>(options =>
            {
                options.ResponseTimeout = clusterOptions.ResponseTimeout;
                options.ResponseTimeoutWithDebugger = clusterOptions.ResponseTimeout;
            });

            this.builder.UseAdoNetClustering(options =>
            {
                options.Invariant = Invariant;
                options.ConnectionString = clusterOptions.AdoNetOptions.GetConnection();
            });


            return this;
        }

        /// <summary>
        /// 启用 Stream
        /// 如使用 UseiCenterClustering 集群请手动注册 AdoNetOptions Service， 否则报错
        /// </summary>
        /// <param name="streamName"></param>
        /// <param name="numOfQueue"></param>
        /// <returns></returns>
        public GasgooClientBuilder UseStreamProvider(string streamName, int numOfQueue = 7)
        {
            //this.builder.UseStreams(streamName, options =>
            //{
            //    options.ConfigurePartitioning(numOfQueue);
            //});

            return this;
        }

        public GasgooClientBuilder ConfigureLogging(Action<ILoggingBuilder> configureLogging)
        {
            this.builder.ConfigureLogging(configureLogging);
            return this;
        }

        public async Task<GasgooClusterHostClient> BuildAndConnectAsync(Action anavailable = null)
        {
            try
            {
                this.anavailable = anavailable;
                var client = this.builder.Build();
                await client.Connect(RetryFilter);
                return new ClusterHostClient(client);
            }
            catch (Exception)
            {
                if (anavailable != null)
                {
                    anavailable.Invoke();
                    return default;
                }
                else
                {
                    throw;
                }
            }
        }

        private GasgooClientBuilder AddFromAppDomain()
        {
            this.builder.ConfigureApplicationParts(parts => parts.AddFromApplicationBaseDirectory().WithReferences());
            return this;
        }

        private async Task<bool> RetryFilter(Exception exception)
        {
            if (exception.GetType() != typeof(SiloUnavailableException))
            {
                Console.WriteLine($"Cluster client failed to connect to cluster with unexpected error.  Exception: {exception}");
                if (this.anavailable != null)
                {
                    this.anavailable.Invoke();
                }
                if (attempt > InitializeAttemptsBeforeFailing)
                    return false;

                attempt += 10;
            }
            attempt++;
            Console.WriteLine($"Cluster client attempt {attempt} of {InitializeAttemptsBeforeFailing} failed to connect to cluster.  Exception: {exception}");
            if (attempt > InitializeAttemptsBeforeFailing)
            {
                return false;
            }
            await Task.Delay(TimeSpan.FromSeconds(1));
            return true;
        }
    }
}
